define('OLG_BLUE', '#81a8e4'); define('OLG_BLUE_INK', '#3f6cae'); define('OLG_ORANGE', '#e4a881'); define('OLG_ORANGE_INK', '#b8672f'); define('OLG_INK', '#171717'); define('OLG_INK_SOFT', '#4a4a4a'); define('OLG_LINE', '#e9e6e1'); define('OLG_SANS', 'Mulish, system-ui, sans-serif'); function olg_esc($s) { return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); } function olg_is_numeric_cell($s) { return (bool) preg_match('/^\$?\d+(\.\d+)?\s*(to|-|\x{2013})?\s*\$?\d*(\.\d+)?\s*(lb|kg|months?|years?|hours?|hrs?|min|minutes?|cups?|kcal|calories?|days?|weeks?|%|x)?\.?$/iu', trim($s)); } function olg_parse_numeric($s) { preg_match_all('/\d+\.?\d*/', $s, $m); if (empty($m[0])) return null; $nums = array_map('floatval', $m[0]); return [$nums[0], end($nums)]; } function olg_wrap_lines($s, $width, $max_lines = 2) { $wrapped = wordwrap($s, $width, "\n", false); $lines = explode("\n", $wrapped); return array_slice($lines, 0, $max_lines); } function olg_svg_line_chart($labels, $series, $title, $ylabel, $aria_label, $width = 640, $height = 460) { $pad_l = 90; $pad_r = 40; $pad_t = 80; $pad_b = 70; $pw = $width - $pad_l - $pad_r; $ph = $height - $pad_t - $pad_b; $all_vals = []; foreach ($series as $pairs) { foreach ($pairs as $p) { $all_vals[] = $p[0]; $all_vals[] = $p[1]; } } $ymin = min($all_vals); $ymax = max($all_vals); $yrange = ($ymax - $ymin) ?: 1; $ymin -= $yrange * 0.08; $ymax += $yrange * 0.08; $n = count($labels); $X = function($i) use ($pad_l, $pw, $n) { return $pad_l + ($pw * $i / max($n - 1, 1)); }; $Y = function($v) use ($pad_t, $ph, $ymin, $ymax) { return $pad_t + $ph - ($ph * ($v - $ymin) / ($ymax - $ymin)); }; $colors = [[OLG_BLUE_INK, OLG_BLUE], [OLG_ORANGE_INK, OLG_ORANGE]]; $parts = []; $parts[] = ''; $parts[] = '' . olg_esc($title) . ''; $steps = 5; for ($i = 0; $i <= $steps; $i++) { $v = $ymin + ($ymax - $ymin) * $i / $steps; $y = $Y($v); $yf = number_format($y, 1, '.', ''); $parts[] = ''; $parts[] = '' . round($v) . ''; } $parts[] = '' . olg_esc($ylabel) . ''; foreach ($labels as $i => $lab) { $parts[] = '' . olg_esc($lab) . ''; } $si = 0; foreach ($series as $name => $pairs) { [$line_c, $fill_c] = $colors[$si % count($colors)]; $lo_pts = []; $hi_pts = []; foreach ($pairs as $i => $p) { $lo_pts[] = number_format($X($i),1,'.','') . ',' . number_format($Y($p[0]),1,'.',''); } foreach (array_reverse($pairs, true) as $i => $p) { $hi_pts[] = number_format($X($i),1,'.','') . ',' . number_format($Y($p[1]),1,'.',''); } $parts[] = ''; $mid_pts = []; foreach ($pairs as $i => $p) { $mid_pts[] = number_format($X($i),1,'.','') . ',' . number_format($Y(($p[0]+$p[1])/2),1,'.',''); } $parts[] = ''; foreach ($pairs as $i => $p) { $parts[] = ''; } $ly = $pad_t + 18 + $si * 30; $parts[] = ''; $parts[] = '' . olg_esc($name) . ''; $si++; } $parts[] = ''; return implode('', $parts); } function olg_svg_key_points_card($rows, $title, $aria_label, $width = 680) { $row_specs = []; foreach ($rows as [$label, $detail]) { $row_specs[] = [$label, olg_wrap_lines($detail, 62, 2)]; } $row_h_base = 46; $line_h = 26; $row_gap = 24; $y = 60; $row_ys = []; foreach ($row_specs as [$label, $lines]) { $row_ys[] = $y; $y += $row_h_base + $line_h * max(count($lines) - 1, 0) + $row_gap; } $height = $y + 12; $parts = []; $parts[] = ''; $parts[] = '' . olg_esc($title) . ''; foreach ($row_specs as $idx => [$label, $lines]) { $ry = $row_ys[$idx]; $parts[] = ''; $parts[] = '' . olg_esc($label) . ''; foreach ($lines as $li => $line) { $parts[] = '' . olg_esc($line) . ''; } $div_y = $ry + $row_h_base + $line_h * max(count($lines)-1,0) + $row_gap - 14; $parts[] = ''; } $parts[] = ''; return implode('', $parts); } function olg_svg_comparison_card($col_a_name, $col_b_name, $rows, $title, $aria_label, $width = 680) { $row_h = 62; $y0 = 108; $height = $y0 + $row_h * count($rows) + 20; $col_a_x = $width * 0.42; $col_b_x = $width * 0.74; $parts = []; $parts[] = ''; $parts[] = '' . olg_esc($title) . ''; $parts[] = '' . olg_esc($col_a_name) . ''; $parts[] = '' . olg_esc($col_b_name) . ''; $parts[] = ''; foreach ($rows as $i => [$label, $a_val, $b_val]) { $ry = $y0 + $i * $row_h; $a_lines = olg_wrap_lines($a_val, 20, 2); $b_lines = olg_wrap_lines($b_val, 20, 2); $n_lines = max(count($a_lines), count($b_lines), 1); $label_y = $ry + 6 + ($n_lines-1)*10; $parts[] = '' . olg_esc($label) . ''; $val_start_y = $ry + 6 - ($n_lines-1)*10; foreach ($a_lines as $li => $line) { $parts[] = '' . olg_esc($line) . ''; } foreach ($b_lines as $li => $line) { $parts[] = '' . olg_esc($line) . ''; } if ($i < count($rows)-1) { $parts[] = ''; } } $parts[] = ''; return implode('', $parts); } function olg_auto_generate_compact($table_headers, $table_rows, $title, $category = null) { $numeric_cols = []; for ($ci = 1; $ci < count($table_headers); $ci++) { $col_cells = []; foreach ($table_rows as $row) { if (isset($row[$ci])) $col_cells[] = $row[$ci]; } if (empty($col_cells)) continue; $numeric_count = 0; foreach ($col_cells as $c) { if (olg_is_numeric_cell($c)) $numeric_count++; } if ($numeric_count / count($col_cells) >= 0.85) $numeric_cols[] = $ci; } if (!empty($numeric_cols)) { $labels = array_map(fn($r) => $r[0], $table_rows); $series = []; foreach ($numeric_cols as $ci) { $parsed = []; $ok = true; foreach ($table_rows as $row) { $p = isset($row[$ci]) ? olg_parse_numeric($row[$ci]) : null; if ($p === null) { $ok = false; break; } $parsed[] = $p; } if ($ok) $series[$table_headers[$ci]] = $parsed; } if (!empty($series)) { $names = array_keys($series); if (count($names) > 1) { $wsets = array_map(fn($n) => array_filter(preg_split('/\W+/', strtolower($n))), $names); $common = array_shift($wsets); foreach ($wsets as $ws) { $common = array_intersect($common, $ws); } $ylabel = !empty($common) ? ucfirst(reset($common)) : 'Value'; } else { $ylabel = $names[0]; } $unit = null; foreach ($table_rows as $row) { foreach ($numeric_cols as $ci) { if (isset($row[$ci]) && preg_match('/\b(lb|kg|months?|years?|hours?|days?|weeks?|cups?|kcal)\b/i', $row[$ci], $m)) { $unit = strtolower($m[1]); break 2; } } } if ($unit && stripos($ylabel, $unit) === false) { $ylabel = "$ylabel ($unit)"; } $aria = "Chart showing $title"; $svg = olg_svg_line_chart($labels, $series, $title, $ylabel, $aria); return ['chart', $svg, ['series'=>$names]]; } } if (count($table_headers) === 3 && $category === 'Dog Breed Comparisons') { $rows = array_map(fn($r) => [$r[0], $r[1] ?? '', $r[2] ?? ''], $table_rows); $aria = "Comparison chart: $title"; $svg = olg_svg_comparison_card($table_headers[1], $table_headers[2], $rows, $title, $aria); return ['comparison_card', $svg, ['row_count'=>count($rows)]]; } $rows_for_card = array_map(fn($r) => [$r[0], count($r) > 1 ? implode(' \xc2\xb7 ', array_slice($r,1)) : ''], $table_rows); $aria = "Key points summary: $title"; $svg = olg_svg_key_points_card($rows_for_card, $title, $aria); return ['card', $svg, ['row_count'=>count($rows_for_card)]]; } function olg_citation_for_category($category, $title) { $t = strtolower($title); if ($category === 'Dog Interactive Tools') { if (strpos($t,'age') !== false || strpos($t,'human year') !== false) { $category = '__senior__'; } elseif (strpos($t,'joint') !== false) { $category = '__joint__'; } elseif (strpos($t,'feed') !== false || strpos($t,'food') !== false || strpos($t,'portion') !== false) { $category = '__feeding__'; } else { $category = '__growth__'; } } $map = [ 'Dog Breed Growth' => '__growth__', 'Dog Puppy Essentials' => '__growth__', 'Dog Exercise & Safety' => '__growth__', 'Dog Exercise & Safety' => '__growth__', 'Dog Breed Comparisons' => '__growth__', 'Dog Breed Traits & Behaviour' => '__growth__', 'Dog Breed Traits & Behaviour' => '__growth__', 'Big Dog Facts' => '__growth__', 'Dog Feeding & Nutrition' => '__feeding__', 'Dog Feeding & Nutrition' => '__feeding__', 'Dog Joint & Mobility Health' => '__joint__', 'Dog Joint & Mobility' => '__joint__', 'Dog Joint & Mobility' => '__joint__', 'Dog Joint & Mobility Health' => '__joint__', 'Senior Big Dog Care' => '__senior__', ]; $key = $map[$category] ?? $category; if (!in_array($key, ['__growth__','__feeding__','__joint__','__senior__'])) { $key = '__growth__'; } $sources = [ '__growth__' => "The American Kennel Club's Chief Veterinary Officer, Dr. Jerry Klein, DVM, has written extensively on the slower growth and development timeline of large and giant breeds. Read more from the AKC's guidance on caring for large-breed dogs.", '__feeding__' => "The World Small Animal Veterinary Association's Global Nutrition Committee publishes evidence-based guidance for evaluating dog food and feeding practices. Read more from the WSAVA Global Nutrition Guidelines.", '__joint__' => "Cornell University's College of Veterinary Medicine covers how growth rate, nutrition, and exercise interact with joint development in large breed dogs. Read more from Cornell's canine hip dysplasia overview.", '__senior__' => "The American Veterinary Medical Association notes that larger breeds are generally considered senior earlier than small breeds, given their shorter typical lifespans. Read more from the AVMA's guidance on caring for senior pets.", ]; return '

' . $sources[$key] . '

'; }